package edu.upe.poli.dsc.sd.clienteSocketUdp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;

import edu.upe.poli.dsc.sd.mensagem.Id;
import edu.upe.poli.dsc.sd.mensagem.Message;
import edu.upe.poli.dsc.sd.mensagem.MessageId;
import edu.upe.poli.dsc.sd.receiver.MessageListener;
import edu.upe.poli.dsc.sd.receiver.MessageReceiver;
import edu.upe.poli.dsc.sd.util.StringUtil;

public class ClienteUDP implements IClienteUDP, MessageReceiver{
	
	private static IClienteUDP instance;
	private static HashMap<Id, Integer> hashCounter = new HashMap<Id, Integer>();
	private HashMap<MessageId, Message> hashMessages;
	private HashMap<MessageId, DatagramPacket> hashDatagram;
	private MessageListener listener = null;
	
	private ClienteUDP(){
		
		this.hashMessages = new HashMap<MessageId, Message>();
		
		this.hashDatagram = new HashMap<MessageId, DatagramPacket>();
	}
	
	public static IClienteUDP getInstance(){
		
		if(instance == null)
			instance = new ClienteUDP();
		
		return instance; 
	}
	
	public Message receive(MessageId messageId){
		
		return hashMessages.remove(messageId);
	}
	
	public void listen(DatagramSocket socket) {
		
		listener = new MessageListener(this, socket);
		
		new Thread(listener).start();
	}
	
	public synchronized String enviarMensagemSincrona(String mensagem, String ip, int porta) {
		
		return enviarMensagemSincrona(null, mensagem, ip, porta);
	}
	
	public synchronized String enviarMensagemSincrona(MessageId messageId, String mensagem, String ip, int porta) {
			
		String sendingMessage = StringUtil.encode(mensagem);
		
		InetAddress host = null;
		
		try {
			
			host = InetAddress.getByName(ip);
			
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		}
		
		if(messageId == null){
			
			Id id = new Id(ip, porta);
			
			Integer sequencia = null;
			
			if((sequencia = hashCounter.get(id)) == null)
				sequencia = 0;
			
			hashCounter.put(id, sequencia + 1);
			
			messageId = new MessageId(host.getHostAddress(), porta, sequencia);
		}
		
		Message m = new Message(messageId, sendingMessage, true);
		
		String[] formattedMessages = m.getFormattedString();
		
		DatagramSocket socket = null;
		
		try {
			
			socket = new DatagramSocket();
			
			listen(socket);
			
			for (int i = 0; i < formattedMessages.length; i++) {
				
				DatagramPacket packet = new DatagramPacket(formattedMessages[i].getBytes(), 
					formattedMessages[i].getBytes().length, host, porta);
				
				socket.send(packet);
			}
			
			Message message = null;
			int tentativas = 0;
			
			/*
			 * Verificando a chegada de mensagens a cada 500 milisegundos por 60 vezes
			 * tempo maximo de espera 30 segundos
			 */
			while(message == null && tentativas < 5){
				
				message = receive(messageId);
				tentativas++;
				
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {}
			}
			
			/*
			 * Se a mensagem nao chegar de volta, tentar novamente
			 */
			if(message == null)
				return enviarMensagemSincrona(messageId, mensagem, ip, porta);
			
			listener.stopListening();
			
			return message.getData();
			
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(socket != null && !socket.isClosed())
				socket.close();
		}
		
		return null;
	}
	
	public void enviarMensagemAssincrona(MessageId messageId, String mensagem, String ip, int porta) {
		
		mensagem = StringUtil.encode(mensagem);
		
		InetAddress host = null;
		
		try {
			
			host = InetAddress.getByName(ip);
			
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		}
		
		Id id = null;
		
		if(messageId == null){
			
			id = new Id(ip, porta);
			
			Integer sequencia = null;
			
			if((sequencia = hashCounter.get(id)) == null)
				sequencia = 0;
			
			hashCounter.put(id, sequencia + 1);
			
			messageId = new MessageId(id.getHost().getHostAddress(), id.getPort(), sequencia);
		}
		
		Message messageSend = new Message(messageId, mensagem, false);
		
		String[] formattedMessages = messageSend.getFormattedString();
		
		DatagramSocket socket = null;
		
		try {
			
			socket = new DatagramSocket();
			
			for (int i = 0; i < formattedMessages.length; i++) {
				
				DatagramPacket packet = new DatagramPacket(formattedMessages[i].getBytes(), 
						formattedMessages[i].getBytes().length, host, porta);
				
				socket.send(packet);
			}
			
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(socket != null && !socket.isClosed())
				socket.close();
		}
	}

	public void enviarMensagemAssincrona(String mensagem, String ip, int porta) {
		
		enviarMensagemAssincrona(null, mensagem, ip, porta);
	}

	public void putMessage(MessageId messageId, Message mensagem) {
		this.hashMessages.put(messageId, mensagem);
	}
	
	public void putDatagram(MessageId messageId, DatagramPacket packet) {
		this.hashDatagram.put(messageId, packet);
	}
}
